import Redis from 'ioredis';
import GlobalAutoConfigurable from "@framework/config/GlobalAutoConfigurable";
import TimeUnit from "@framework/enum/TimeUnit";
import KoaBootLogger from './KoaBootLogger';

/**
 * redis中间件
 */
class KoaBootRedis {

    /**
     * 日志组件
     */
    private logger: KoaBootLogger = KoaBootLogger.getInstance();

    /**
     * ioRedis客户端
     * @private
     */
    private redis: Redis;

    /**
     * 构造函数
     */
    constructor() {
        this.redis = new Redis({
            host: GlobalAutoConfigurable.koaBoot.redis.host,
            port: GlobalAutoConfigurable.koaBoot.redis.port,
            db: GlobalAutoConfigurable.koaBoot.redis.database,
            username: GlobalAutoConfigurable.koaBoot.redis.username,
            password: GlobalAutoConfigurable.koaBoot.redis.password
        });
    }

    /**
     * 监听Redis是否实例化成功
     */
    public async onRedis(): Promise<string> {
        return new Promise<string>((resolve, reject) => {
            this.redis.on('connect', () => {
                resolve("success");
            });
            this.redis.on('error', (error) => {
                this.logger.error("Initialize Redis" + error);
                resolve("error");
            });
        });
    }

    /**
     * 设置缓存对象值
     * @param key 键
     * @param value 值
     * @param unit 时间单位
     * @param time 时间
     */
    public setCacheObject<T>(key: string, value: T, unit?: TimeUnit, time?: number): void {
        if (unit && time) {
            const newTime: number = this.getTimeByUnit(unit, time);
            this.redis.set(key, JSON.stringify(value), "PX", newTime);
        } else {
            this.redis.set(key, JSON.stringify(value));
        }
    }

    /**
     * 获取缓存对象值
     * @param key 键
     * @returns 任意类型
     */
    public async getCacheObject<T>(key: string): Promise<T | undefined> {
        const value = await this.redis.get(key);
        if (value) {
            return  JSON.parse(value);
        }
        return undefined;
    }

    /**
     * 删除缓存的数据KEY
     * @param key 键
     */
    public deleteCacheObject(key: string): void {
        this.redis.del(key);
    }

    /**
     * 设置Map数据
     * @param key 键
     * @param data Map数据
     */
    public setCacheMap<K, V>(key: string, value: Map<K, V>, unit?: TimeUnit, time?: number): void {
        if (unit && time) {
            const newTime: number = this.getTimeByUnit(unit, time);
            this.redis.set(key, JSON.stringify([...value]), "PX", newTime);
        } else {
            this.redis.set(key, JSON.stringify(value));
        }
    }

    /**
     * 获取Map数据
     * @param key 键
     */
    public async getCacheMap<K,V>(key: string): Promise<Map<K, V> | undefined> {
        const value = await this.redis.get(key);
        if (value) {
            return new Map<K, V>(JSON.parse((value as string)));
        }
        return undefined;
    }

    /**
     * 自增
     * @param key 键
     */
    public incr(key: string, number: number, unit?: TimeUnit, time?: number): void {
        if (unit && time) {
            this.redis.incrby(key, number);
            this.expire(key, unit, time);
        } else {
            this.redis.incrby(key, 1);
        }
    }

    /**
     * 自减
     * @param key 键
     */
    public decr(key: string, number: number, unit?: TimeUnit, time?: number): void {
        if (unit && time) {
            this.redis.decrby(key, number);
            this.expire(key, unit, time);
        } else {
            this.redis.decrby(key, number);
        }
    }

    /**
     * 队列，左插入
     * @param key 键
     * @param value 值
     */
    public lPush<T>(key: string, value: T): void {
        this.redis.lpush(key, JSON.stringify(value));
    }

    /**
     * 多列。左取出
     * @param key
     */
    public async lPop<T>(key: string): Promise<T | undefined> {
        const value = this.redis.lpop(key);
        if (value) {
            return value as unknown as T;
        }
        return undefined;
    }

    /**
     * 队列，右插入
     * @param key 键
     * @param value 值
     */
    public rPush<T>(key: string, value: T): void {
        this.redis.rpush(key, JSON.stringify(value));
    }

    /**
     * 队列，右插入
     * @param key
     */
    public async rPop<T>(key: string): Promise<T | undefined> {
        const value = await this.redis.rpop(key);
        if (value) {
            return value as unknown as T;
        }
        return undefined;
    }

    /**
     * 命名空间
     * @param key
     * @param namepace
     * @param value
     */
    public hSet<T>(key: string, namespace: string, value: T): void {
        this.redis.hset(key, namespace, JSON.stringify(value));
    }

    /**
     * 命名空间
     * @param key
     * @param namepace
     * @param value
     */
    public async hGet<T>(key: string, namespace: string): Promise<T | undefined> {
        const value = await this.redis.hget(key, namespace);
        if (value) {
            return value as unknown as T;
        }
        return undefined;
    }

    /**
     * 同步，判断键是否存在
     * @param key 键
     */
    public async exists(key: string): Promise<boolean> {
        const exists: number = await this.redis.exists(key);
        return exists > 0;
    }

    /**
     * 设置过期时间
     * @param key 键
     * @param time 时间（单位默认毫秒）
     * @param unit 时间单位
     */
    public expire(key: string, unit: TimeUnit, time: number): void {
        const timeByUnit = this.getTimeByUnit(unit, time);
        this.redis.pexpire(key, timeByUnit);
    }

    /**
     * 根据时间单位获取时间
     * @param unit 时间单位
     * @param time 时间
     * @private
     */
    private getTimeByUnit(unit: TimeUnit, time: number) {
        switch (unit) {
            case TimeUnit.DAYS:
                return time * 60 * 60 * 24 * 1000;
            case TimeUnit.HOURS:
                return time * 60 * 60 * 1000;
            case TimeUnit.MINUTES:
                return time * 60 * 1000;
            case TimeUnit.SECONDS:
                return time * 1000;
            case TimeUnit.MILLISECONDS:
                return time;
        }
    }
}

/**
 * 自定义错误类型
 */
export interface Error {
    code: string;
    message: string;
    stack?: string;
}

export default KoaBootRedis;
